package cn.org.xwb.common.file.utils;

import cn.org.xwb.common.file.dto.FileSliceUploadDto;
import cn.org.xwb.common.file.enmus.FileUploadStatus;
import cn.org.xwb.common.file.vo.FileSliceUploadResultVo;
import io.minio.messages.Item;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

@Component
@Log4j2
public class FileUploadUtil {

    private FileUploadUtil() {}

    @Autowired
    private MinioUtil minioUtil;

    /**
     * 生成文件上传成功的返回结果对象
     *
     * @param fileUrl
     * @return
     */
    public FileSliceUploadResultVo genFileSliceUploadSuccessResultVo(String fileUrl) {
        return new FileSliceUploadResultVo(
                FileUploadStatus.FILE_UPLOAD_SUCCESS.getStatus(),
                FileUploadStatus.FILE_UPLOAD_SUCCESS.getMessage(),
                fileUrl
        );
    }

    /**
     * 生成文件开始上传的返回结果对象
     *
     * @return
     */
    public FileSliceUploadResultVo genFileSliceUploadStartResultVo() {
        return new FileSliceUploadResultVo(
                FileUploadStatus.FILE_UPLOAD_START.getStatus(),
                FileUploadStatus.FILE_UPLOAD_START.getMessage(),
                ""
        );
    }

    /**
     * 生成文件上传失败的返回结果对象
     *
     * @return
     */
    public FileSliceUploadResultVo genFileSliceUploadFailResultVo() {
        return new FileSliceUploadResultVo(
                FileUploadStatus.FILE_UPLOAD_FAIL.getStatus(),
                FileUploadStatus.FILE_UPLOAD_FAIL.getMessage(),
                ""
        );
    }

    /**
     * 生成文件上传中的返回结果对象
     *
     * @return
     */
    public FileSliceUploadResultVo genFileSliceUploadUploadingResultVo(Integer nextChunkIdx) {
        return new FileSliceUploadResultVo(
                nextChunkIdx,
                FileUploadStatus.FILE_UPLOAD_ING.getMessage(),
                ""
        );
    }

    /**
     * 生成文件上传完成的返回结果对象
     *
     * @return
     */
    public FileSliceUploadResultVo genFileSliceUploadUploadedResultVo(Integer nextChunkIdx) {
        return new FileSliceUploadResultVo(
                nextChunkIdx,
                FileUploadStatus.FILE_UPLOAD_ED.getMessage(),
                ""
        );
    }

    /**
     * 获取上传到 minio 的文件的访问地址，文件访问有效时间默认为 minio 最长有效时间，
     * 如需永久需要在 minio 中进行配置，该方法默认返回 ？ 后面截取后的 url
     *
     * @param fileSliceUploadDto 文件分片上传信息对象
     * @return 获取失败返回 null，否则返回上传到 minio 的文件的访问地址
     */
    public String getFileUrl(FileSliceUploadDto fileSliceUploadDto) {
        String fileUrl = minioUtil.getFileUrl(
                fileSliceUploadDto.getBucket(),
                fileSliceUploadDto.getMinioFilename(),
                7, TimeUnit.DAYS
        );
        if (Objects.isNull(fileUrl)) return "";
        fileUrl = fileUrl.substring(0, fileUrl.lastIndexOf("?"));
        return fileUrl;
    }

    /**
     * 获取上传到 minio 的文件的访问地址，文件访问有效时间默认为 minio 最长有效时间，
     * 如需永久需要在 minio 中进行配置，该方法默认返回 ？ 后面截取后的 url
     *
     * @param bucket 桶
     * @param minioFilename minio 中文件对名称
     * @return 获取失败返回 null，否则返回上传到 minio 的文件的访问地址
     */
    public String getFileUrl(String bucket,  String minioFilename) {
        String fileUrl = minioUtil.getFileUrl(bucket, minioFilename, 7, TimeUnit.DAYS);
        if (Objects.isNull(fileUrl)) return "";
        fileUrl = fileUrl.substring(0, fileUrl.lastIndexOf("?"));
        return fileUrl;
    }

    /**
     * 分片合并，清除分片缓存
     *
     * @param bucket
     * @param chunkDirName
     * @param md5
     * @param minioFilename
     * @return true-成功；false-失败
     */
    public Boolean chunkMergeAndClear(String bucket, String chunkDirName, String md5, String minioFilename) {
        FileSliceUploadDto fileSliceUploadDto = new FileSliceUploadDto();
        fileSliceUploadDto.setBucket(bucket);
        fileSliceUploadDto.setDirName(chunkDirName);
        fileSliceUploadDto.setMd5(md5);
        fileSliceUploadDto.setMinioFilename(minioFilename);
        return chunkMergeAndClear(fileSliceUploadDto);
    }

    /**
     * 分片合并，清除分片缓存
     *
     * @param fileSliceUploadDto
     * @return true-成功；false-失败
     */
    public Boolean chunkMergeAndClear(FileSliceUploadDto fileSliceUploadDto) {
        // 分片文件和用于存储分片合并后的文件
        File chunkFile = null;
        File mergeFile = null;
        // 输出输入流
        RandomAccessFile raf_write = null;
        RandomAccessFile raf_read = null;
        try {
            chunkFile = File.createTempFile("chunk", null);
            mergeFile = File.createTempFile("merge", null);
            // 创建临时合并文件的流对象，用于合并分块，向存储分片合并后的文件输出
            raf_write = new RandomAccessFile(mergeFile, "rw");
            // 获取所有的文件分片对象
            List<Item> objs = minioUtil.getObjs(
                    fileSliceUploadDto.getBucket(),
                    fileSliceUploadDto.getDirName() + "/",
                    false
            );
            // 分片合并
            for (Item obj : objs) {
                // 当前要进行合并的分片
                chunkFile = minioUtil.downloadFileFromMinio(chunkFile, fileSliceUploadDto.getBucket(), obj.objectName());
                // 读取分块文件的流对象，从分片文件中输入
                raf_read = new RandomAccessFile(chunkFile, "r");
                byte[] b = new byte[1024];
                int len = -1;
                while ((len = raf_read.read(b)) != -1) {
                    // 向合并文件写数据，输出
                    raf_write.write(b, 0, len);
                }
            }
            // 检验合并文件的 md5
            String mergeFileMD5 = FileUtil.getFileMD5(mergeFile);
            if (!mergeFileMD5.equals(fileSliceUploadDto.getMd5())) {
                return false;
            }
            // 合并文件上传到 minio
            Boolean res = minioUtil.addFileToMinio(fileSliceUploadDto.getBucket(), mergeFile, fileSliceUploadDto.getMinioFilename());
            if (!res) return res;
            // 删除缓存分片文件
            return minioUtil.removeFolder(fileSliceUploadDto.getBucket(), fileSliceUploadDto.getDirName());
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            if (chunkFile != null) {
                chunkFile.delete();
            }
            if (mergeFile != null) {
                mergeFile.delete();
            }
            if (raf_write != null) {
                try {
                    raf_write.close();
                } catch (IOException e) {

                }
            }
            if (raf_read != null) {
                try {
                    raf_read.close();
                } catch (IOException e) {

                }
            }
        }
    }

}
